﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PayloadTracker.OSM
{
    public delegate void TilePreacherEvent();
    /// <summary>
    /// Class that precaches tiles for offline access.
    /// </summary>
    class TilePrecacher
    {
        private class WorkOrder
        {
            public System.Drawing.Point Point;
            public int Zoom;
            public WorkOrder(System.Drawing.Point p, int z)
            {
                Point = p;
                Zoom = z;
            }
        }

        public event TilePreacherEvent Start;
        public event TilePreacherEvent Finish;
        public event TilePreacherEvent Update;


        Coordinate m_UL;
        Coordinate m_LR;
        public int m_ThreadCount;
        public List<System.Threading.Thread> m_Threads;
        List<WorkOrder> m_WorkOrders;
        int m_WorkIterator;
        bool m_Finished;
        /// <summary>
        /// Progress of precaching between 0 and 1
        /// </summary>
        public float Progress
        {
            get { return ((float)m_WorkIterator) / ((float)m_WorkOrders.Count); }
        }
        /// <summary>
        /// Number of tiles already downloaded.
        /// </summary>
        public int Completed
        {
            get { return m_WorkIterator; }
        }
        /// <summary>
        /// Total number of tiles to download.
        /// </summary>
        public int Total
        {
            get { return m_WorkOrders.Count;  }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="threads">number of download threads to use.</param>
        public TilePrecacher(int threads)
        {
            m_ThreadCount = threads;
            m_Threads = new List<System.Threading.Thread>();
            m_WorkOrders = new List<WorkOrder>();
            m_Finished = false;
        }
        /// <summary>
        /// Precache tile range.
        /// </summary>
        /// <param name="center">Center point from which to cache</param>
        /// <param name="mileradius">Number of US Miles to cache from the radius.</param>
        /// <param name="minzoom">Minimum zoom level to cache.</param>
        /// <param name="maxzoom">Maximum zoom level to cache.</param>
        public void Precache(Coordinate center, float mileradius, int minzoom, int maxzoom)
        {
            //assuming 0.01° =  ~ 1 km 

            double degreeoffset = mileradius * 1.609344 * 0.01;

            m_UL = new Coordinate(center.Lat + degreeoffset, center.Long - degreeoffset);
            m_LR = new Coordinate(center.Lat - degreeoffset, center.Long + degreeoffset);

            foreach (var t in m_Threads)
                t.Abort();

            m_Threads.Clear();

            lock (m_WorkOrders)
                for (int i = minzoom; i <= maxzoom; i++)
                {
                    var ulp = Tile.Coordinate2Tile(m_UL, i);
                    var lrp = Tile.Coordinate2Tile(m_LR, i);

                    for (int x = Math.Max(0, ulp.X - 1); x <= lrp.X; x++)
                    {
                        for (int y = Math.Max(0, ulp.Y - 1); y <= lrp.Y; y++)
                        {
                            m_WorkOrders.Add(new WorkOrder(new System.Drawing.Point(x, y), i));
                        }
                    }

                }
            m_WorkIterator = 0;
            m_Finished = false;


            //spin up threads.
            for (int i = 0; i < m_ThreadCount; i++)
            {
                var t = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadLoop));
                t.Start();
                m_Threads.Add(t);
            }
            TilePreacherEvent handler = Start;
            if (handler != null)
                handler();
        }
        /// <summary>
        /// Cancel precaching
        /// </summary>
        public void Cancel()
        {
            foreach (var t in m_Threads)
                t.Abort();
            m_Threads.Clear();
            m_WorkOrders.Clear();
        }
        
        private WorkOrder GetNextWorkTile()
        {
            if (!m_Finished)
                lock (m_WorkOrders)
                {
                    if (m_WorkIterator + 1 < m_WorkOrders.Count)
                    {
                        WorkOrder ret = m_WorkOrders[m_WorkIterator];
                        m_WorkIterator++;

                        TilePreacherEvent handler = Update;
                        if (handler != null)
                            handler();

                        return ret;
                    }
                    else
                    {
                        m_WorkIterator++;
                        m_Finished = true;
                        TilePreacherEvent handler = Update;
                        if (handler != null)
                            handler();

                        handler = Finish;
                        if (handler != null)
                            handler();

                        return null;
                    }
                }
            else
                return null;
        }

        private void ThreadLoop()
        {
            WorkOrder order;
            while ((order = GetNextWorkTile()) != null)
            {
                Tile t = new Tile(order.Point.X,order.Point.Y,order.Zoom,false);
            }
        }
    }
}
